//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2005-2007
//
// GPX files export.
//

# include "StdAfx.h"
# include "Save.h"
# include "Map.h"
# include "Status.h"
# include "SaveFileDlg.h"
# include "FileWriter.h"
# include "fast_floor.h"
# include "Globals.h"
# include "Version.h"

class CGpxExport : public CMapSaver {
  public:
	virtual bool CanSave (const map_t & _map) {return true;}
	virtual bool Save (HANDLE _hFile, const char * _strFileName, const map_t & _map);

  private:
	bool _Save (HANDLE _hFile, const map_t & _map);

	void _SaveWaypoint (CFileWriter & _fw, const rgn_t & _rgn, const rgn_origin_t  & _origin);
	void _SaveTrack    (CFileWriter & _fw, const rgn_t & _rgn);
	void _SaveRoute    (CFileWriter & _fw, const rgn_t & _rgn, const rgn_element_t & _element, bool _bElement0th);
};

DECLARE_WPTRK_EXPORT (CGpxExport, "topografix-gpx", "gpx", "GPS eXchange format (*.gpx)");

static
void _WriteDateTime (CFileWriter & _fw, DATE _dt, bool _bHasMilliseconds = false) {
	// ISO 8601 'dateTime' type
	// Sample: 2001-11-28T21:05:28Z

	SYSTEMTIME st;
	::VariantTimeToSystemTime (_dt, & st);

	_fw.WriteU (st.wYear);
	_fw.WriteC ('-');

	if (st.wMonth < 10)
		_fw.WriteC ('0');
	_fw.WriteU (st.wMonth);
	_fw.WriteC ('-');

	if (st.wDay < 10)
		_fw.WriteC ('0');
	_fw.WriteU (st.wDay);
	_fw.WriteC ('T');

	if (st.wHour < 10)
		_fw.WriteC ('0');
	_fw.WriteU (st.wHour);
	_fw.WriteC (':');

	if (st.wMinute < 10)
		_fw.WriteC ('0');
	_fw.WriteU (st.wMinute);
	_fw.WriteC (':');

	if (st.wSecond < 10)
		_fw.WriteC ('0');
	_fw.WriteU (st.wSecond);
	if (_bHasMilliseconds) {
		const double dfSeconds = _dt*24*60*60;
		const int nMilliseconds = fast_floor_d ((dfSeconds - fast_floor_d (dfSeconds))*1000);

		_fw.WriteC ('.');
		_fw.WriteU (nMilliseconds);
	}

	_fw.WriteC ('Z');
}

static
void _WriteText (CFileWriter & _fw, const char * _str, size_t _cLen) {
	for (size_t c = 0; c < _cLen; ++ c) {
		const char ch = _str [c];
		if (ch == '\0') {
			_cLen = c;
			break;
		}
		if (ch == '<' || ch == '>' || ch == '&') {
			// NOTE: data island required.
			_fw.WriteS ("<![CDATA[");
			_fw.WriteS (_str, _cLen);
			_fw.WriteS ("]]>");
			return;
		}
	}

	_fw.WriteS (_str, _cLen);
}

static
void _WriteText (CFileWriter & _fw, const string_t & _str) {
	_WriteText (_fw, _str.c_str (), _str.length ());
}

static
void _WriteUtf8 (CFileWriter & _fw, const string_t & _str) {
	const size_t cLen = _str.size ();

	// Convert ANSI to Unicode.
	const size_t cLen0 = cLen*3;
	wchar_t * const wstr = (wchar_t *) ::alloca (sizeof (wchar_t)*cLen0);
	const size_t cLen1 = ::MultiByteToWideChar (CP_ACP, 0, _str.c_str (), cLen, wstr, cLen0);

	// Convert Unicode to UTF-8.
	const size_t cLen2 = cLen1*3;
	char * const str = (char *) ::alloca (sizeof (char)*cLen2);
	const size_t cLen3 = ::WideCharToMultiByte (CP_UTF8, 0, wstr, cLen1, str, cLen2, NULL, NULL);

	_WriteText (_fw, str, cLen3);
}

void CGpxExport::_SaveWaypoint (CFileWriter & _fw, const rgn_t & _rgn, const rgn_origin_t & _origin) {
	const waypoint_info_t * const pWptExtra = _rgn.pExtra->wtr.wpt ();
	assert (pWptExtra != NULL);

	_fw.WriteS ("<wpt lat=\"");
	_fw.WriteF (_origin.point.y);
	_fw.WriteS ("\" lon=\"");
	_fw.WriteF (_origin.point.x);
	_fw.WriteS ("\">\r\n");

	// Altitude.
	if (pWptExtra->wFlags & waypoint_info_t::defAltitude) {
		_fw.WriteS (" <ele>");
		_fw.WriteF (pWptExtra->fAltitude);
		_fw.WriteS ("</ele>\r\n");
	}

	// Date and Time.
	if (pWptExtra->wFlags & waypoint_info_t::defTime) {
		_fw.WriteS (" <time>");
		_WriteDateTime (_fw, pWptExtra->dtTime);
		_fw.WriteS ("</time>\r\n");
	}

	// Name.
	_fw.WriteS (" <name>");
	_WriteUtf8 (_fw, _rgn.strLabel);
	_fw.WriteS ("</name>\r\n");

	// Description.
	if (! _rgn.strComment.empty ()) {
		_fw.WriteS (" <desc>");
		_WriteUtf8 (_fw, _rgn.strComment);
		_fw.WriteS ("</desc>\r\n");
	}

	// Link.
	if (pWptExtra && ! pWptExtra->strURL.empty ()) {
		_fw.WriteS (" <url>");
		_WriteText (_fw, pWptExtra->strURL);
		_fw.WriteS ("</url>\r\n");
	}

	_fw.WriteS ("</wpt>\r\n");
}

void CGpxExport::_SaveTrack (CFileWriter & _fw, const rgn_t & _rgn) {
	const track_info_t * const pTrkExtra = _rgn.pExtra->wtr.track ();

	_fw.WriteS ("<trk>\r\n");

	// Name.
	_fw.WriteS (" <name>");
	_WriteUtf8 (_fw, _rgn.strLabel);
	_fw.WriteS ("</name>\r\n");

	// Description.
	if (! _rgn.strComment.empty ()) {
		_fw.WriteS (" <desc>");
		_WriteUtf8 (_fw, _rgn.strComment);
		_fw.WriteS ("</desc>\r\n");
	}

	// Link.
	if (pTrkExtra && ! pTrkExtra->strURL.empty ()) {
		_fw.WriteS (" <url>");
		_WriteText (_fw, pTrkExtra->strURL);
		_fw.WriteS ("</url>\r\n");
	}

	for (size_t cElement = 0; cElement < _rgn.elements.size (); ++ cElement) {
		const rgn_element_t & element = _rgn.elements [cElement];
		if (element.bDeleted)
			continue;

		const points_t & points = element.points;
		const size_t cPoints = points.size ();
		const size_t cPointsExtra = element.pExtra->TrackPoints.size ();

		_fw.WriteS (" <trkseg>\r\n");

		for (size_t cPoint = 0; cPoint < cPoints; ++ cPoint) {
			_fw.WriteS ("  <trkpt lat=\"");
			_fw.WriteF (points [cPoint].y);
			_fw.WriteS ("\" lon=\"");
			_fw.WriteF (points [cPoint].x);
			_fw.WriteS ("\">\r\n");

			if (cPoint < cPointsExtra) {
				const trackpoint_info_t & PointExtra = element.pExtra->TrackPoints [cPoint];

				// Altitude.
				if (PointExtra.wFlags & trackpoint_info_t::defAltitude) {
					_fw.WriteS ("   <ele>");
					_fw.WriteF (PointExtra.fAltitude);
					_fw.WriteS ("</ele>\r\n");
				}

				// Date and Time.
				if (PointExtra.wFlags & trackpoint_info_t::defTime) {
					_fw.WriteS ("   <time>");
					_WriteDateTime (_fw, PointExtra.dtTime, (PointExtra.wFlags & trackpoint_info_t::defTimeMilliseconds) != 0);
					_fw.WriteS ("</time>\r\n");
				}

				// Speed.
				if (PointExtra.wFlags & trackpoint_info_t::defSpeed) {
					_fw.WriteS ("   <speed>");
					_fw.WriteF (PointExtra.fSpeed/3.6);
					_fw.WriteS ("</speed>\r\n");
				}

				// DOP.
				if (PointExtra.wFlags & trackpoint_info_t::defDOP) {
					_fw.WriteS ("   <pdop>");
					_fw.WriteF (PointExtra.fDOP);
					_fw.WriteS ("</pdop>\r\n");
				}
			}

			_fw.WriteS ("  </trkpt>\r\n");
		}

		_fw.WriteS (" </trkseg>\r\n");
	}

	_fw.WriteS ("</trk>\r\n");
}

void CGpxExport::_SaveRoute (CFileWriter & _fw, const rgn_t & _rgn, const rgn_element_t & _element, bool _bElement0th) {
	if (_bElement0th == false)
		return;

	const points_t & points = _element.points;
	const size_t cPoints = points.size ();

	const route_info_t * const pRtExtra = _rgn.pExtra->wtr.route ();
	const size_t cPointsExtra = pRtExtra ? pRtExtra->points.size () : 0;

	_fw.WriteS ("<rte>\r\n");

	// Name.
	_fw.WriteS (" <name>");
	_WriteUtf8 (_fw, _rgn.strLabel);
	_fw.WriteS ("</name>\r\n");

	// Description.
	if (! _rgn.strComment.empty ()) {
		_fw.WriteS (" <desc>");
		_WriteUtf8 (_fw, _rgn.strComment);
		_fw.WriteS ("</desc>\r\n");
	}

	// Link.
	if (pRtExtra && ! pRtExtra->strURL.empty ()) {
		_fw.WriteS (" <url>");
		_WriteText (_fw, pRtExtra->strURL);
		_fw.WriteS ("</url>\r\n");
	}

	for (size_t cPoint = 0; cPoint < cPoints; ++ cPoint) {
		_fw.WriteS (" <rtept lat=\"");
		_fw.WriteF (points [cPoint].y);
		_fw.WriteS ("\" lon=\"");
		_fw.WriteF (points [cPoint].x);
		_fw.WriteS ("\">\r\n");

		if (pRtExtra && cPoint < cPointsExtra) {
			const routepoint_info_t & PointExtra = pRtExtra->points [cPoint];
			const rgn_t * const pWpt = PointExtra.pWaypoint;
			if (pWpt) {
				const waypoint_info_t * const pWptExtra = pWpt->pExtra->wtr.wpt ();
				assert (pWptExtra != NULL);

				// Altitude.
				if (pWptExtra->wFlags & waypoint_info_t::defAltitude) {
					_fw.WriteS ("  <ele>");
					_fw.WriteF (pWptExtra->fAltitude);
					_fw.WriteS ("</ele>\r\n");
				}

				// Date and Time.
				if (pWptExtra->wFlags & waypoint_info_t::defTime) {
					_fw.WriteS ("  <time>");
					_WriteDateTime (_fw, pWptExtra->dtTime);
					_fw.WriteS ("</time>\r\n");
				}

				// Name.
				_fw.WriteS ("  <name>");
				_WriteUtf8 (_fw, pWpt->strLabel);
				_fw.WriteS ("</name>\r\n");

				// Description.
				if (! pWpt->strComment.empty ()) {
					_fw.WriteS ("  <desc>");
					_WriteUtf8 (_fw, pWpt->strComment);
					_fw.WriteS ("</desc>\r\n");
				}

				// Link.
				if (! pWptExtra->strURL.empty ()) {
					_fw.WriteS ("  <url>");
					_WriteText (_fw, pWptExtra->strURL);
					_fw.WriteS ("</url>\r\n");
				}
			}
		}

		_fw.WriteS (" </rtept>\r\n");
	}

	_fw.WriteS ("</rte>\r\n");
}

bool CGpxExport::Save (HANDLE _hFile, const char * _strFileName, const map_t & _map) {
	if (_hFile != NULL) {
		return _Save (_hFile, _map);
	} else {
		//
		// Prepare default file name.
		//
		char strFileName [MAX_PATH + 1];
		::strncpy (strFileName, _strFileName, MAX_PATH);
		strFileName [MAX_PATH] = '\0';
		if (! SaveFileDlg ("gpx", "GPS eXchange format (*.gpx)\0*.gpx\0", strFileName))
			return false;

		//
		// Open the file.
		//
		const CFileForWrite hFile (strFileName);
		if (! hFile.IsValid ()) {
			ErrorMsgBox ("Can't create file:\n %s", strFileName);
			return false;
		}

		return _Save (hFile, _map);
	}
}

bool CGpxExport::_Save (HANDLE _hFile, const map_t & _map) {
	CFileWriter fw (_hFile);
	fw.SetFPrecision (6);

	//
	// Write the file.
	//
	SetProgressStatus (0);

	// XML header.
	fw.WriteS (
		"<\?xml version=\"1.0\" encoding=\"UTF-8\" \?>\r\n"
		"<gpx\r\n"
		" version=\"1.0\"\r\n"
		" creator=\"GPSMapEdit " GME_VER_S " - http://www.geopainting.com\"\r\n"
		" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n"
		" xmlns=\"http://www.topografix.com/GPX/1/0\"\r\n"
		" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\r\n"
	);

	// Date and Time.
	SYSTEMTIME stCurrent;
	::GetSystemTime (& stCurrent);
	DATE dtCurrent;
	::SystemTimeToVariantTime (& stCurrent, & dtCurrent);
	fw.WriteS ("<time>");
	_WriteDateTime (fw, dtCurrent);
	fw.WriteS ("</time>\r\n");

	// Bounds.
	fw.WriteFmt (
		"<bounds minlat=\"%0.6f\" minlon=\"%0.6f\" maxlat=\"%0.6f\" maxlon=\"%0.6f\"/>\r\n",
		_map.rectBound.y0, _map.rectBound.x0, _map.rectBound.y1, _map.rectBound.x1
	);

	// Save waypoints.
	size_t cPoint = 0;
	const layer_t & point_layer = _map.layers [rkPoint];
	const size_t cPoints = point_layer.cRgns;
	const rgns_t & points = point_layer.rgns;
	rgns_t::const_iterator iRgn;
	for (iRgn = points.begin (); iRgn != points.end (); ++ iRgn, ++ cPoint) {
		const rgn_t & rgn = * iRgn;
		if (rgn.IsDeleted () || ! rgn.IsWaypoint ())
			continue;

		for (size_t cOrigin = 0; cOrigin < rgn.origins.size (); ++ cOrigin) {
			const rgn_origin_t & origin = rgn.origins [cOrigin];
			if (origin.bDeleted)
				continue;

			_SaveWaypoint (fw, rgn, origin);
		}

		SetProgressStatus (fast_floor (30.f*cPoint/cPoints + .5f));
	}

	// Save tracks and routes.
	size_t cPolyline = 0;
	const layer_t & polyline_layer = _map.layers [rkPolyline];
	const size_t cPolylines = polyline_layer.cRgns;
	const rgns_t & polylines = polyline_layer.rgns;
	for (iRgn = polylines.begin (); iRgn != polylines.end (); ++ iRgn, ++ cPolyline) {
		const rgn_t & rgn = * iRgn;
		if (rgn.IsDeleted () || ! rgn.IsWTR ())
			continue;

		if (rgn.IsTrack ()) {
			_SaveTrack (fw, rgn);
		} else {
			assert (rgn.IsRoute ());

			for (size_t cElement = 0; cElement < rgn.elements.size (); ++ cElement) {
				const rgn_element_t & element = rgn.elements [cElement];
				if (element.bDeleted)
					continue;

				assert (rgn.IsRoute ());
				_SaveRoute (fw, rgn, element, cElement == 0);
			}
		}

		SetProgressStatus (30 + fast_floor (70.f*cPolyline/cPolylines + .5f));
	}

	fw.WriteS ("</gpx>");

	fw.FlushBuffer ();

	SetProgressStatus (100);

	if (fw.GetOverflow ())
		ErrorMsgBox ("File has been written partialy.\nThis may be caused by disk overflow.");

	return true;
}